//
//  GamePad.cpp
//  Snake
//
//  Created by CmST0us on 16/11/25.
//  Copyright © 2016年 SpringEngine. All rights reserved.
//

#include "GamePad.hpp"
#include <ncurses.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#include "Food.hpp"
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include "Frame.hpp"
#define TOP_ROW     4           /* top_row */
#define BOT_ROW     LINES - 1
#define LEFT_EDGE   0
#define RIGHT_EDGE  COLS - 1
#define PLAYGROUND_ROW TOP_ROW + 1
#define PLAYGROUND_COL 1

GamePadRef GamePad::shareInstance(){
    static GamePadRef gamePad;
    if (gamePad == nullptr) {
        gamePad = new GamePad();
        return gamePad;
    }
    return gamePad;
}
void* timeThreadRun(void* ctx){
    GamePadRef p = (GamePadRef)ctx;
    event_base_dispatch(p->_eventBase);
    return NULL;
}
Point GamePad::generateFoodPoint(){
    Point foodPoint;
    do{
        int x = rand() % (COLS - 2) + 1;
        int y = rand() % (LINES - TOP_ROW - 2) + TOP_ROW + 1;
        foodPoint.x = x;
        foodPoint.y = y;
    }while (this->_snake._body->searchBody(foodPoint) != NULL);
    return foodPoint;
}

GamePad::GamePad(){
//    Init Game Windows
    char wall = ' ';
    int i, j;
    initscr();
    cbreak();               /* put termial to CBREAK mode */
    noecho();
    curs_set(0);            /* set cursor invisible */
    
    /* display some message about title and wall */
    attrset(A_NORMAL);      /* set NORMAL first */
    attron(A_REVERSE);      /* turn on REVERSE to display the wall */
    for(i = 0; i < LINES; i++)
    {
        mvaddch(i, LEFT_EDGE, wall);
        mvaddch(i, RIGHT_EDGE, wall);
    }
    for(j = 0; j < COLS; j++)
    {
        mvaddch(0, j, '=');
        mvaddch(TOP_ROW, j, wall);
        mvaddch(BOT_ROW, j, wall);
    }
    attroff(A_REVERSE);     /* turn off REVERSE */
    mvaddstr(1, 2, "Game: Snake    version: 1.0    date: 2016/11/25");
    mvaddstr(2, 2, "Author: CmST0us   Company: SpringEngine");
    mvaddstr(3, 2, "Usage: \"w\" to up, \"s\" to down, \"a\" to left, \"d\" to right");
    refresh();
    
    this->_eventBase = event_base_new();
    
    EventRef timerEvent = evtimer_new(this->_eventBase, GamePad::gamePadWillUpdateView, this);
    struct timeval time;
    time.tv_usec = 500e3;
    time.tv_sec = 0;
    evtimer_add(timerEvent, &time);
    _food = new Food(this->generateFoodPoint());
//    this->_sem = sem_open("keyInputSem", O_CREAT, 0644, 0);
//    postTimes = 0;
    
    this->_speedValue = 1;
    this->_frame.orig.x = LEFT_EDGE;
    this->_frame.orig.y = TOP_ROW;
    this->_frame.size.x = RIGHT_EDGE;
    this->_frame.size.y = BOT_ROW;
}

void GamePad::run(){
    pthread_t p;
    pthread_create(&p, NULL, timeThreadRun, this);
}
void GamePad::listenKeyInput(){
    char c;
    while (1) {
//        sem_wait(this->_sem);
//        postTimes--;
        c = getch();
        switch (c) {
            case GamePad::keyUP:{
                if (this->_snake._direction.y == 1 && this->_snake._direction.x == 0) {
                    continue;
                }
                if (this->_snake._direction.y == -1 && this->_snake._direction.x == 0) {
                    continue;
                }
                this->_snake._direction.y = -1;
                this->_snake._direction.x = 0;
            }
                break;
            case GamePad::keyDown:{
                if (this->_snake._direction.y == -1 && this->_snake._direction.x == 0) {
                    continue;
                }
                if (this->_snake._direction.y == 1 && this->_snake._direction.x == 0) {
                    continue;
                }
                this->_snake._direction.y = 1;
                this->_snake._direction.x = 0;
            }
                break;
            case GamePad::keyLeft:{
                if (this->_snake._direction.y == 0 && this->_snake._direction.x == 1) {
                    continue;
                }
                if (this->_snake._direction.y == 0 && this->_snake._direction.x == -1) {
                    continue;
                }
                this->_snake._direction.y = 0;
                this->_snake._direction.x = -1;
            }
                break;
            case GamePad::keyRight:{
                if (this->_snake._direction.y == 0 && this->_snake._direction.x == -1) {
                    continue;
                }
                if (this->_snake._direction.y == 0 && this->_snake._direction.x == 1) {
                    continue;
                }
                this->_snake._direction.y = 0;
                this->_snake._direction.x = 1;
            }
                break;
            case GamePad::keySpeedUp:{
                if (this->_speedValue <= 8) {
                    this->_speedValue++;
                }
            }
                break;
            case GamePad::keySpeedDown:{
                if (this->_speedValue >= 1) {
                    this->_speedValue--;
                }
                break;
            }
            default:
                break;
        }
    }
}
void clearScreen(){
    attrset(A_NORMAL);
    //clear
    for (int i = PLAYGROUND_ROW; i < BOT_ROW; i++) {
        for (int j = PLAYGROUND_COL; j < RIGHT_EDGE; j++) {
            mvaddch(i, j, ' ');
        }
    }
}
void GamePad::endGame(){
//    int len = this->_snake._body->bodyLength();
//    for (int i = 0; i < len; ++i) {
//        this->_snake._body->delBody(i);
//        this->_snake.bodyToHead();
//    }
    
     mvaddstr(LINES/2, COLS/3 - 4, "Game Over!!!!");
     refresh();
    delete this->_food;
    event_base_loopbreak(this->_eventBase);
    event_base_free(this->_eventBase);
    getchar();
    endwin();
    exit(0);
    
}
void GamePad::reDrawPlayGround(){
    clearScreen();
    //redraw
    BodyRef pNode = this->_snake._body;
    for (; ; ) {
        mvaddch(pNode->point.y, pNode->point.x, pNode->shape);
        if (pNode->next == nullptr) {
            break;
        }else{
            pNode = pNode->next;
        }
    }
    mvaddch(this->_food->point.y, this->_food->point.x, this->_food->shape);
    
    refresh();
}
void GamePad::gamePadWillUpdateView(EventFd fd, short event, void* ctx){
    //snake move
    
    GamePadRef ctxPad = GamePad::shareInstance();
    
    switch (ctxPad->_snake.move(ctxPad->_food, ctxPad->_frame)) {
        case Snake::STATE_NORMAL:
        {
            //next go
        }
            break;
        case Snake::STATE_SELFKILL:{
            //end game
            ctxPad->endGame();
        }
            break;
        case Snake::STATE_WALL:{
            //end game
            ctxPad->endGame();
        }
            break;
        case Snake::STATE_FOOD:{
            //next go
            ctxPad->_food->point = ctxPad->generateFoodPoint();
        }
            break;
        default:
            break;
    }
    //update View
    ctxPad->reDrawPlayGround();
//    [FIXME]: 快速按键会出现回头杀问题
//    if (ctxPad->postTimes == 0) {
//        sem_post(ctxPad->_sem);
//        ctxPad->postTimes++;
//    }
    
    EventRef timerEvent = evtimer_new(ctxPad->_eventBase, GamePad::gamePadWillUpdateView, ctx);
    struct timeval time;
    time.tv_usec = 1000e3 - 100e3 * ctxPad->_speedValue;
    time.tv_sec = 0;
    evtimer_add(timerEvent, &time);
    
}